package de.fhwf.mixedreality.client2008;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.List;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

import de.fhwf.mixedreality.client.IClient;
import de.fhwf.mixedreality.client.Vector;
import de.fhwf.mixedreality.client.IClient.Team;

public class XMLProtocol {
	public final static String PROTOCOL_VERSION = "1.0";

	public static String ClientToServerConnect(String nickname, int vtID, int rcID) {
		Element nde;
		Element root = new Element("connect");

		nde = new Element("type");
		nde.setText("Client");
		root.addContent(nde);

		nde = new Element("protocol_version");
		nde.setText(PROTOCOL_VERSION);
		root.addContent(nde);

		nde = new Element("nickname");
		nde.setText(nickname);
		root.addContent(nde);

		nde = new Element("rc_id");
		nde.setText(Integer.toString(rcID));
		root.addContent(nde);

		nde = new Element("vt_id");
		nde.setText(Integer.toString(vtID));
		root.addContent(nde);

		return getXMLString(root);
	}

	public static String ClientToServerConnect1_1() {
		Element nde;
		Element root = new Element("connect");

		nde = new Element("type");
		nde.setText("Client");
		root.addContent(nde);

		nde = new Element("protocol_version");
		nde.setText("1.1");
		root.addContent(nde);

		return getXMLString(root);
	}

	public static boolean ServerToClientConnect(String xml) {
		Element root = getXMLNode(xml);
		if (root.getName().equals("connect"))  {
			Element e = root.getChild("type");
			if (e != null) {
				if (e.getText().equals("Client")) {
					return true;
				}
			}
		}
		return false;
	}

	@SuppressWarnings("unchecked")
	public static boolean WorldDataApplication(MessageParser messageParser, PositionsStorage positions, String xml) {
/* Sample data:
<WorldData>
	<time>226.603</time>
	<agent_id>0</agent_id>
	<nickname>Bot 0</nickname>
	<status>found</status>
	<max_agent>2</max_agent>
	<playMode>play on</playMode>
	<score>
		<yellow>0</yellow>
		<blue>0</blue>
	</score>
	<wheel_velocities>
		<right>0</right>
		<left>0</left>
	</wheel_velocities>
	<ball>
		<dist>49</dist>
		<angle>158</angle>
	</ball>
	<opponent>
		<id>1</id>
		<nickname>Bot 1</nickname>
		<status>not_found</status>
		<dist>1729</dist>
		<angle>-166</angle>
		<orientation>73</orientation>
	</opponent>
	<flag>
		<id>top_left_pole</id>
		<dist>252</dist>
		<angle>161</angle>
	</flag>
	<flag>
		<id>bottom_left_pole</id>
		<dist>251</dist>
		<angle>146</angle>
	</flag>
	<flag>
		<id>top_right_pole</id>
		<dist>155</dist>
		<angle>-42</angle>
	</flag>
	<flag>
		<id>bottom_right_pole</id>
		<dist>153</dist>
		<angle>-17</angle>
	</flag>
	<flag>
		<id>top_left_corner</id>
		<dist>287</dist>
		<angle>-178</angle>
	</flag>
	<flag>
		<id>bottom_left_corner</id>
		<dist>282</dist>
		<angle>124</angle>
	</flag>
	<flag>
		<id>top_right_corner</id>
		<dist>207</dist>
		<angle>-71</angle>
	</flag>
	<flag>
		<id>bottom_right_corner</id>
		<dist>200</dist>
		<angle>13</angle>
	</flag>
	<flag>
		<id>top_left_goal</id>
		<dist>150</dist>
		<angle>-176</angle>
	</flag>
	<flag>
		<id>bottom_left_goal</id>
		<dist>145</dist>
		<angle>123</angle>
	</flag>
	<flag>
		<id>top_right_goal</id>
		<dist>84</dist>
		<angle>-99</angle>
	</flag>
	<flag>
		<id>bottom_right_goal</id>
		<dist>75</dist>
		<angle>40</angle>
	</flag>
	<flag>
		<id>top_left_small_area</id>
		<dist>215</dist>
		<angle>145</angle>
	</flag>
	<flag>
		<id>bottom_left_small_area</id>
		<dist>217</dist>
		<angle>162</angle>
	</flag>
	<flag>
		<id>top_right_small_area</id>
		<dist>118</dist>
		<angle>-14</angle>
	</flag>
	<flag>
		<id>bottom_right_small_area</id>
		<dist>121</dist>
		<angle>-46</angle>
	</flag>
	<flag>
		<id>middle_center</id>
		<dist>49</dist>
		<angle>158</angle>
	</flag>
	<flag>
		<id>top_center</id>
		<dist>150</dist>
		<angle>-137</angle>
	</flag>
	<flag>
		<id>bottom_center</id>
		<dist>140</dist>
		<angle>82</angle>
	</flag>
</WorldData>
 */
		
		Element root = getXMLNode(xml);
		if (root.getName().equals("WorldData")) {
			
			Element e = root.getChild("time");
			if (e != null) {
				messageParser.setTime((int)(Double.parseDouble(e.getText()) * 1000));
			}

			e = root.getChild("agent_id");
			if (e != null) {
				messageParser.setID(Integer.parseInt(e.getText()));
			}

			e = root.getChild("status");
			if (e != null) {
  			if (e.getText().equals("found")) {
  				messageParser.setLost(false);
  			} else {
  				messageParser.setLost(true);
  				// cancel processing
  				return false;
  			}
			}

			e = root.getChild("playMode");
			if (e != null) {
				String mode = e.getText();
				messageParser.setPlayMode(mode);
			}

			e = root.getChild("score");
			if (e != null) {
				int y = 0;
				int b = 0;
				Element e2 = e.getChild("yellow");
				if (e2 != null) {
					y = Integer.parseInt(e2.getText());
				}
				e2 = e.getChild("blue");			
				if (e2 != null) {
					b = Integer.parseInt(e2.getText());
				}
				messageParser.setScore(y, b);
			}

			e = root.getChild("ball");
			if (e != null) {
				positions.setBallPos(parseVector(e));
			}

			List<Element> opponents = root.getChildren("opponent");
			int i = 0;
			for (Element opponent : opponents) {
				e = opponent.getChild("status");
				if (e != null) {
	  			if (e.getText().equals("found")) {
	  				double o = 0;
	  				e = opponent.getChild("orientation");
	  				if (e != null) {
	  					o = Double.parseDouble(e.getText());
	  				}
	  				Vector v = parseVector(opponent);
  					messageParser.setOpponentUpdated(i);
	  				if (!v.isZero()) {
	  					positions.setOpponentPos(i, v, o);
	  				}
	  			}
				}
				i++;
			}

			List<Element> teammates = root.getChildren("teammate");
			if (teammates.size() == 0) {
				teammates = root.getChildren("teamMate");
			}
			i = 0;
			for (Element teammate : teammates) {
				e = teammate.getChild("status");
				if (e != null) {
	  			if (e.getText().equals("found")) {
	  				double o = 0;
	  				e = teammate.getChild("orientation");
	  				if (e != null) {
	  					o = Double.parseDouble(e.getText());
	  				}
	  				Vector v = parseVector(teammate);
  					messageParser.setTeammateUpdated(i);
	  				if (!v.isZero()) {
	  					positions.setTeammatePos(i, v, o);
	  				}
	  			}
				}
				i++;
			}

			List<Element> flags = root.getChildren("flag");
			for (Element flag : flags) {
				Vector v = parseVector(flag);
				if (v.getName().equals("top_left_corner")) {
					positions.setLeftCornerFlag1(v);
				} else if (v.getName().equals("bottom_left_corner")) {
						positions.setLeftCornerFlag2(v);
				} else if (v.getName().equals("top_right_corner")) {
					positions.setRightCornerFlag1(v);
				} else if (v.getName().equals("bottom_right_corner")) {
					positions.setRightCornerFlag2(v);
				} else if (v.getName().equals("top_left_pole")) {
					positions.setLeftGoalPole1(v);
				} else if (v.getName().equals("bottom_left_pole")) {
					positions.setLeftGoalPole2(v);
				} else if (v.getName().equals("top_right_pole")) {
					positions.setRightGoalPole1(v);
				} else if (v.getName().equals("bottom_right_pole")) {
					positions.setRightGoalPole2(v);
				}
			}
			return true;
		}
		return false;
	}

	private static Vector parseVector(Element xmlVector) {
		Element e;
		double angle = 0;
		double length = 0;
		String name = "";
		
		if (xmlVector != null) {
  		e = xmlVector.getChild("angle");
  		if (e != null) {
  			double v = Double.parseDouble(e.getValue());
  			angle = Math.toRadians(v);
  		}
  		e = xmlVector.getChild("dist");
  		if (e != null) {
  			length = Double.parseDouble(e.getValue());
  		}
  		e = xmlVector.getChild("id");
  		if (e != null) {
  			name = e.getValue();
  		}
		}
		return new Vector(angle, length, name);
	}

	public static String sendWorldDataApplication(PositionsStorage positions, Team team, boolean isLost) {
		//Vector v;
		Element e;
	  int myid = 0; //client.myID();

		Element root = new Element("WorldData");

		e = new Element("time");
		e.setText("0.0");
		root.addContent(e);

		e = new Element("agent_id");
		e.setText(Integer.toString(myid));
		root.addContent(e);

		e = new Element("status");
		if (isLost) {
			e.setText("lost");
		} else {
			e.setText("found");
		}
		root.addContent(e);

		e = new Element("playMode");
		e.setText("play on");
		root.addContent(e);

		// Fake opponents to show them in view
		for (int i = 0; i < IClient.MAX_PLAYERS; i++) {
			if (!positions.opponent(i).isZero()) {
    		e = new Element("opponent");
        Element e2 = new Element("status");
        e2.setText("found");
        e.addContent(e2);
    		root.addContent(e);
			}
		}

		// Fake teammates to show them in view
		for (int i = 0; i < IClient.MAX_PLAYERS; i++) {
			if (!positions.teammate(i).isZero() || i == 0) {
    		e = new Element("teammate");
        Element e2 = new Element("status");
        e2.setText("found");
        e.addContent(e2);
    		root.addContent(e);
			}
		}

		/*
		e = new Element("ball");
		xmlVector(e, positions.ball(), "");
		root.addContent(e);

		e = new Element("flag");
		if (team == Team.BLUE) {
		 v = positions.myCornerFlag1();
		} else {
		 v = positions.opCornerFlag1();
		}
 	  xmlVector(e, v, "top_left_corner");
		root.addContent(e);

		e = new Element("flag");
		if (team == Team.BLUE) {
		 v = positions.myCornerFlag2();
		} else {
		 v = positions.opCornerFlag2();
		}
 	  xmlVector(e, v, "bottom_left_corner");
		root.addContent(e);

		e = new Element("flag");
		if (team == Team.BLUE) {
		 v = positions.opCornerFlag1();
		} else {
		 v = positions.myCornerFlag1();
		}
 	  xmlVector(e, v, "top_right_corner");
		root.addContent(e);

		e = new Element("flag");
		if (team == Team.BLUE) {
		 v = positions.opCornerFlag2();
		} else {
		 v = positions.myCornerFlag2();
		}
 	  xmlVector(e, v, "bottom_right_corner");
		root.addContent(e);
*/
		return getXMLString(root);		
	}
	
/*
	private static void xmlVector(Element root, Vector v, String id) {
		Element e = new Element("angle");
		e.setText(Integer.toString((int)Math.toDegrees(v.getAngle())));
		root.addContent(e);

		e = new Element("dist");
		e.setText(Double.toString(v.getLength()));
		root.addContent(e);

		if (!id.equals("")) {
			e = new Element("id");
			e.setText(id);
			root.addContent(e);
		}
	}
*/
	
	public static String sendConnectionResponse(boolean status) {
		Element root = new Element("connect");
		if (status) {
			root.setText("true");
		} else {
			root.setText("false");			
		}

		return getXMLString(root);		
	}
	
	public static boolean receiveConnectionResponse(String xml) {
		Element root = getXMLNode(xml);
		if (root.getName().equals("connect")) {
			if (root.getValue().equals("true")) {
				return true;
			}
		}
		return false;
	}

	public static String sendParameters(int rcID, int vtID, String nickname, String teamName) {
		Element nde;
		Element root = new Element("parameters");

		nde = new Element("rc_id");
		nde.setText(Integer.toString(rcID));
		root.addContent(nde);

		nde = new Element("vt_id");
		nde.setText(Integer.toString(vtID));
		root.addContent(nde);

		nde = new Element("nickname");
		nde.setText(nickname);
		root.addContent(nde);

		nde = new Element("teamname");
		nde.setText(teamName);
		root.addContent(nde);

		return getXMLString(root);		
	}

	public static boolean receiveParameters(String xml) {
		Element root = getXMLNode(xml);
		if (root.getName().equals("parameters")) {
			Element e = root.getChild("properties");
			if (e != null) {
				return true;
			}
		} else if (root.getName().equals("properties")) {
				Element e = root.getChild("properties");
				if (e != null) {
					return true;
				}
		}
		return false;
	}

	public static String CommandWheelVelocities(int left, int right) {
		Element root = new Element("command");
		Element vel = new Element("wheel_velocities");

		Element a = new Element("right");
		a.setText(Integer.toString(right));
		vel.addContent(a);

		Element f = new Element("left");
		f.setText(Integer.toString(left));
		vel.addContent(f);

		root.addContent(vel);
		
		return getXMLString(root);				
	}

	public static String CommandKick(double angle, double force) {
		Element root = new Element("command");
		Element kick = new Element("kick");

		Element a = new Element("angle");
		a.setText(Double.toString(Math.toDegrees(angle)));
		kick.addContent(a);

		Element f = new Element("force");
		f.setText(Double.toString(force));
		kick.addContent(f);

		root.addContent(kick);
		
		return getXMLString(root);				
	}

	public static String CommandGoto(double dist, double angle) {
		Element root = new Element("command");
		Element got0 = new Element("goto");

		Element f = new Element("dist");
		f.setText(Double.toString(dist));
		got0.addContent(f);

		Element a = new Element("angle");
		a.setText(Double.toString(Math.toDegrees(angle)));
		got0.addContent(a);

		root.addContent(got0);
		
		return getXMLString(root);				
	}

	private static String getXMLString(Element root) {
		Document doc = new Document(root);
		try {
			StringWriter writer = new StringWriter();
			XMLOutputter serializer = new XMLOutputter(Format.getPrettyFormat());
			serializer.output(doc, writer);
			String out = writer.toString();

			// HACK: Current soccer server (DeluxeML?) does not like valid XML...
			out = out.replace("\r", "");
			out = out.replace("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", "");
			while (out.charAt(out.length() - 1) == '\n') {
				out = out.substring(0, out.length() - 1);
			}
			
			return out;
		} catch (IOException e1) {
			System.err.println("[XMLProtocol] getXMLString: Error writing XML:");
			e1.printStackTrace();
		}
		return "";
	}

	private static Element getXMLNode(String xml) {

		// String can have surplus 0-bytes at the end, so remove them
		String in = xml;
		int end = in.indexOf('\0');
		if (end > 0) {
		  in = in.substring(0, end);
		}
		
		// Remove possible separator
		if (in.charAt(in.length() - 1) == '|') {
			in = xml.substring(0, in.length() - 1);
		}

		//System.out.println("[XMLProtocol] getXMLNode: Parsing " + in);

		SAXBuilder builder = new SAXBuilder();
		StringReader reader = new StringReader(in);
		try {
			Document doc = builder.build(reader);
			return doc.getRootElement();
		} catch (JDOMException e) {
			System.err.println("[XMLProtocol] getXMLNode: XML string not valid:");
			System.err.println(e.getMessage());
		} catch (IOException e) {
			System.err.println("[XMLProtocol] getXMLNode: Error reading string:");
			e.printStackTrace();
		}
		return null;
	}
}
